// scaup - Scala up!
// (c) 2009-2010, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 94 2010-06-12 15:35:36Z normen.mueller $
package locutor.rws.gm.parsing

import java.io.File

import collection.mutable.HashSet

import scaup.Views._

import locutor.rws.gm._
import locutor.util.parsing._

/** A graph model parser.
 * 
 *  <p>Note the fact that we can't parse expressions like ``const operatorMap:map<dom_AssignmentOperatorKind, string> = { ... }'', yet</p>
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.5
 */
private [gm] class Parser extends ExpressionParser { 
  /* Fill in abstract defs
   */
  type Tokens = Lexer
  
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing
   */                                 
  lexical.reserved ++= new HashSet[String] ++ List(    
    "abstract", "arbitrary", "boolean", "copy", "const", "class", "connect", "directed", "enum", "edge", "extends", "graphmodel", "inherited", "map",
    "node", "object", "undirected", "set"
  ) ++ ExpressionParser.reserved
  
  //lexical.delimiters ++= new HashSet[String] ++ List("-->", "<--", "?--?", "--", ";", "=") ++ ExpressionParser.delimiters
  lexical.delimiters ++= new HashSet[String] ++ List(",", "{", "}", "[", "]", "<", ">", "::", ":", "->", "+", "-->", "<--", "?--?", "--", ";", "=")
  
  /* Define the grammar
   */
  lazy val gm = ("graphmodel" ~> ident) ~ (opt(model_extends) <~ "{") ~ (enum*) ~ ((clazz*) <~ "}") ^^ {
    case name ~ models ~ enums ~ clazz =>
      val (nodes, edges) = (clazz partition {case _: NodeType => true case _ => false}) match {
        case (nodes, edges) => (nodes.asInstanceOf[List[NodeType]], edges.asInstanceOf[List[EdgeType]])
      }
      new GraphModel(name, enums, nodes, edges) join (models match {
        case Some(models) => models 
        case None         => Nil
      })
  }
  
  private lazy val model_extends = ("extends" ~> (ext ~ (("," ~> ext)*))) ^^ {
    case u ~ us => (u :: us) map (r => GraphModel.parse((r.toLowerCase + ".gm").toFile))
  }  
  
  private lazy val ext = accept("string",  {case lexical.StringLit(n) => n})
  
  private lazy val enum = ("enum" ~> annotated_ident) ~ ("{" ~> repsep((annotated_ident ~ opt("=" ~> expr)), ",")) <~ "}" ^^ {
    case id ~ Nil     => EnumType(id, None)
    case id ~ entries => EnumType(id, Some((Map[AnnotatedIdentifier, Option[String]]() /: entries)((m,e) => e match { case k ~ v => m + {(k, v)} })))
  }   
  
  implicit def os2b(o: Option[String]) = o match {
    case Some(_) => true
    case _       => false
  }  
  
  private lazy val clazz =
    ( opt("abstract") ~ opt("const") ~ node_class ^^ {
      case abst ~ cons ~ ((name, exts, attrs)) => NodeType(abst, cons, name, exts, attrs)
    }
    | opt("abstract") ~ opt("const") ~ edge_class ^^ {
      case abst ~ cons ~ ((dir, name, exts, asss, attrs)) => EdgeType(abst, cons, dir, name, exts, asss, attrs)
    }
    )  
  
  private lazy val node_class =
    ( ("node" ~ "class") ~> (annotated_ident ~ opt(class_extends)) <~ ";" ^^ {
      case id ~ ex => (id, ex match { case Some(ex) => ex case _ => Nil}, Nil)
    }
    | ("node" ~ "class") ~> (annotated_ident ~ opt(class_extends)) ~ ("{" ~> ((class_attribute)*) <~ "}") ^^ {
      case id ~ ex ~ as => (id, ex match { case Some(ex) => ex case _ => Nil}, as)
    }
    )  
  
  private lazy val edge_class =
    ( opt(direction) ~ (("edge" ~ "class") ~> annotated_ident) ~ opt(class_extends) ~ (opt(connect) <~ ";") ^^ {
      case dir ~ id ~ ex ~ cs => (dir, id, ex match { case Some(ex) => ex case _ => Nil}, cs match { case Some(cs) => cs case _ => Nil}, Nil)
    }
    | opt(direction) ~ (("edge" ~ "class") ~> annotated_ident) ~ opt(class_extends) ~ opt(connect) ~ ("{" ~> ((class_attribute)*) <~ "}") ^^ {
      case dir ~ id ~ ex ~ cs ~ as => (dir, id, ex match { case Some(ex) => ex case _ => Nil}, cs match { case Some(cs) => cs case _ => Nil}, as)
    }
    )
  
  private lazy val direction =("arbitrary" ^^^ {"arbitrary "} | "undirected" ^^^ {"undirected "} | "directed" ^^^ {"directed "})
  
  private lazy val connect = "connect" ~> (assertion ~ (("," ~> assertion)*)) ^^ {case c ~ cs => c :: cs}
  
  private lazy val assertion =
    ( "copy" ~ "extends" ^^^ { ImplicitAssertion }
    | ident ~ opt(range) ~ ("-->" | "<--" | "--" | "?--?") ~ ident ~ opt(range) ^^ {
      case dom ~ domr ~ d ~ cod ~ codr => ExplicitAssertion(RangedIdentifier(dom, domr), d, RangedIdentifier(cod, codr))
    }
    )  
  
  private lazy val class_extends = "extends" ~> (ident ~ (("," ~> ident)*)) ^^ {case i ~ is => (i :: is) map (Identifier(_))}
  
  private lazy val class_attribute = 
    ( annotated_ident ~ (":" ~> class_attribute_types) ~ opt("=" ~> expr) <~ ";" ^^ {
      case id ~ t ~ e => Attribute(id, Some(t), e)
    }
    | annotated_ident ~ ("=" ~> expr <~ opt(";")) ^^ {
      case id ~ e => Attribute(id, None, Some(e))
    }
    )  
  
  private lazy val class_attribute_types =
    ( ident
    | primitives
    | ("set" ~ "<") ~> primitives <~ ">" ^^ {case p => "set<"+p+">"}
    | ("map" ~ "<") ~> (primitives <~ ",") ~ (primitives <~ ">") ^^ {case k~v => "map<"+k+","+v+">"}
    )  
  
  private lazy val annotated_ident = ident ~ opt(annotation) ^^ {case id ~ as => AnnotatedIdentifier(id, as)}
  
  private lazy val annotation = "[" ~> (ident ~ opt("=" ~> constant)) ~ (("," ~> (ident ~ opt("=" ~> constant)))*) <~ "]" ^^ {
    case hd ~ tl => (Map[String, Option[String]]() /: (hd :: tl))((m,a) => a match { case key ~ value => m + {(key, value)} })
  }
  
  override lazy val string = accept("string",  {case lexical.StringLit(n) => n mkString ("\"", "", "\"") })
}
